/*
    vega
    Copyright (c) Vlad Dimitriu

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>

#include <self.h>
#include <http.h>

static void read_connection(struct epoll_event *events, int i)
{
	int done = 0;
	int s;
	ssize_t count;
	char buf[512];
	char *response = "HTTP/1.0 200 OK\n"
		"Content-type: text/plain\n"
		"Content-length: 3\r\n\r\nOK!";

	while (1) {
		count = read(events[i].data.fd, buf, sizeof buf);
		if (count == -1) {
			if (errno != EAGAIN) {
				done = 1;
			}
			break;
		} else if (count == 0) {
			done = 1;
			break;
		}

		s = write(1, buf, count);
		write(events[i].data.fd, response, strlen(response));
		if (s == -1) {
			v_log(ERROR, "cannot write to socket");
			abort();
		}
	}

	if (done) {
		close(events[i].data.fd);
	}

}

static int make_socket_non_blocking(int sfd)
{
	int flags, s;

	flags = fcntl(sfd, F_GETFL, 0);
	if (flags == -1) {
		perror("fcntl");
		return -1;
	}

	flags |= O_NONBLOCK;
	s = fcntl(sfd, F_SETFL, flags);
	if (s == -1) {
		perror("fcntl");
		return -1;
	}

	return 0;
}

static void connect_incomming_requests(int efd, int sfd,
				struct epoll_event *event)
{
	int s;
	struct sockaddr in_addr;
	socklen_t in_len;
	int infd;
	char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
	in_len = sizeof (in_addr);
	int i;
	while (1) {
		infd = accept(sfd, &in_addr, &in_len);
		if (infd == -1)	{
			if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
				return;
			} else {
				v_log(ERROR, "cannot accept incoming request");
				break;
			}
		}

		s = getnameinfo(&in_addr, in_len,
				hbuf, sizeof hbuf,
				sbuf, sizeof sbuf,
				NI_NUMERICHOST | NI_NUMERICSERV);
		if (s == 0) {
			printf("Accepted connection on descriptor %d "
				"(host=%s, port=%s)\n", infd, hbuf, sbuf);
		}

		/* Make the incoming socket non-blocking and add it to the
		   list of fds to monitor. */
		s = make_socket_non_blocking(infd);
		if (s == -1) {
			v_log(ERROR, "cannot make accepted socket nonblocking");
			abort ();
		}

		event->data.fd = infd;
		event->events = EPOLLIN | EPOLLET;
		s = epoll_ctl(efd, EPOLL_CTL_ADD, infd, event);
		if (s == -1) {
			v_log(ERROR, "cannot add connected socket to epoll");
			abort();
		}
	}
}

static int create_and_bind()
{
	extern struct self s;
	int error, sfd;
	struct sockaddr_in address;

	address.sin_family = AF_INET;
	address.sin_port = htons(atoi(s.port));
	inet_pton(AF_INET, s.ip, &address.sin_addr.s_addr);

	sfd = socket(PF_INET, SOCK_STREAM, 0);
	if (sfd == -1) {
		v_log(ERROR, "cannot open socket");
		exit(EXIT_FAILURE);
	}

	error = bind(sfd, (struct sockaddr*)&address, sizeof address);
	if (error == 0) {
		v_log(DEBUG, "bind succeded\n");
	}

	return sfd;
}


int epoll_socket(void) {
	int sfd, s;
	int efd;
	struct epoll_event event;
	struct epoll_event *events;

	sfd = create_and_bind();
	if (sfd == -1) {
		v_log(ERROR, "cannot bind");
		abort();
	}

	s = make_socket_non_blocking(sfd);
	if (s == -1) {
		v_log(ERROR, "cannot configure socket");
		abort();
	}

	s = listen(sfd, SOMAXCONN);
	if (s == -1) {
		v_log(ERROR, "cannot listen on socket");
		abort();
	}

	efd = epoll_create1(0);
	if (efd == -1) {
		v_log(ERROR, "cannot create epoll");
		abort();
	}

	event.data.fd = sfd;
	event.events = EPOLLIN | EPOLLET;
	s = epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event);
	if (s == -1) {
		v_log(ERROR, "cannot use epoll");
		abort();
	}

	/* Buffer where events are returned */
	events = calloc (MAXEVENTS, sizeof event);

	/* The event loop */
	while (1) {
		int n, i;
		n = epoll_wait(efd, events, MAXEVENTS, -1);
		for (i = 0; i < n; i++) {
			if ((events[i].events & EPOLLERR) ||
				(events[i].events & EPOLLHUP) ||
				(!(events[i].events & EPOLLIN))) {
				close(events[i].data.fd);
				continue;
			} else if (sfd == events[i].data.fd) {
				connect_incomming_requests(efd, sfd, &event);
				continue;
			} else {
				read_connection(events, i);
			}
		}
	}

	free(events);
	close(sfd);

	return 0;
}
